package tab

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"gopkg.in/xmlpath.v2"

	"code.google.com/awwwe-browser/auralvm/conversation"
	"code.google.com/awwwe-browser/auralvm/param"
	"code.google.com/awwwe-browser/auralvm/setting"
	"code.google.com/awwwe-browser/data"
	"code.google.com/awwwe-browser/scrub"
	"code.google.com/awwwe-browser/scrub/htmlsoup"
)

// HandlerTabFormSubmit handles an http request to submit a form for the view.
func HandlerTabFormSubmit(w http.ResponseWriter, r *http.Request) {
	var ok bool
	var values []string
	var tabkey, contentindex, tabindex int
	var intro string
	var tab *Tab
    var tabnew Tab
	var senddata url.Values
	var c, conv *conversation.Conversation
	var i, length, last int
	var formcontroloption *param.FormControlOption
	var err error
	var jsonbb []byte
	r.ParseForm()
	// tab key
	values, ok = r.Form[param.ParamTabKey]
	if !ok || (ok && len(values) != 1) {
		intro = "I was not able to speak a web page to you because the voice app did not provide the tab key."
		NextContinuer(w, r, intro)
		return
	}
	tabkey, ok = data.StringToInt(values[0])
	if !ok {
		intro = "I was not able to speak a web page to you because the voice app did not provide a valid tab key."
		NextContinuer(w, r, intro)
		return
	}
	length = BrowserTabs.Len()
	if length == 0 {
		intro = "You do not have any tabs yet."
		NextContinuer(w, r, intro)
		return
	}
	ok = false
	for tabindex = 0; tabindex < length; tabindex++ {
		tab = BrowserTabs.AtIndex(tabindex)
		if tab.Key == tabkey {
			ok = true
			break
		}
	}
	if !ok {
		intro = "I was not able to speak a web page to you because the voice app did not provide a valid tab key."
		NextContinuer(w, r, intro)
		return
	}
	// content index
	values, ok = r.Form[param.ParamTabContentIndex]
	if !ok || (ok && len(values) != 1) {
		if !ok {
			intro = "I was not able to speak a web page to you because the voice app did not provide the content index."
			NextContinuer(w, r, intro)
			return
		}
	} else {
		contentindex, ok = data.StringToInt(values[0])
		if !ok {
			intro = "I was not able to speak a web page to you because the voice app did not provide a valid content index."
			NextContinuer(w, r, intro)
			return
		}
	}
	// check for required while building the data
	senddata = url.Values{}
	ok = true
	length = len(tab.Contents)
	last = length - 1
requiredloop:
	for i = 1; i < last; i++ {
		// iterate through the form elements
		c = tab.Contents[contentindex][i]
		switch c.Type {
		case conversation.ConversationTypeFormEnterText:
			if c.Required && len(c.TextValue.UserValue) == 0 {
				// missing data so send this conversation to the user
				conv = c
				conv.Intro = "before you can submit this form you must speak some text into this text box."
				ok = false
				break requiredloop
			} else {
				if len(c.TextValue.UserValue) > 0 {
					senddata.Add(c.Name, c.TextValue.UserValue)
				}
			}
		case conversation.ConversationTypeFormSingleSelect, conversation.ConversationTypeFormMultipleSelect:
			if c.Required {
				ok = false
				for _, formcontroloption = range c.MenuOptions {
					if formcontroloption.UserSelected == true {
						ok = true
						break
					}
				}
			}
			if ok {
				for _, formcontroloption = range c.MenuOptions {
					if formcontroloption.UserSelected == true {
						senddata.Add(c.Name, formcontroloption.Value)
					}
				}
			} else {
				// missing data so re-send this conversation to the user
				conv = c
				conv.Intro = "before you can submit this form you must select an item in this menu."
				break requiredloop
			}
		}
	}
	if ok {
		// send data, get the reponse
		last = len(tab.Contents[contentindex]) - 1
		conv = tab.Contents[contentindex][last]
		if conv.Method == "get" {
			tabnew, err = buildTabGet(conv.Action, senddata)
		} else {
			tabnew, err = buildTabPost(conv.Action, senddata)
		}
		if err != nil {
			intro = fmt.Sprintf("I was not able to submit your information because ", err.Error())
			NextContinuer(w, r, intro)
			return
		}
		BrowserTabs.ReplaceAt(tabindex, &tabnew)
		ContinuerTabListen(w, r, data.EmptyString, tab.Key, -1, 0, 0, true)
	} else {
		// don't submit the form
		// missing data
		// takes some kind of hack to get here.
		jsonbb, err = conv.Marshal()
		if err == nil {
			w.Write(jsonbb)
		} else {
			NextContinuer(w, r, data.EmptyString)
		}
	}
}

// rebuildFormElementList rebuilds the headers for the form conversation which iterates the form elements.
// the headers reflect if the form elements are filled or not.
// so the listener knows right away if the element needs edited.
func rebuildFormElementList(tab *Tab, contentindex int, contentoptionindex int) {
	var conversations []*conversation.Conversation
	var src, dst *conversation.Conversation
	var i, last int
	var option *conversation.APIOption
	var text string
	var texts []string
	var fco *param.FormControlOption
	var count int
	var optionindex int
	conversations = tab.Contents[contentindex]
	dst = conversations[0]
	last = len(conversations) - 1
	optionindex = 0
	for i = 1; i < last; i++ {
		src = conversations[i]
		option = &dst.Options[optionindex]
		optionindex++
		option.VXMLs.Truncate(1)
		switch src.Type {
		case conversation.ConversationTypeFormEnterText:
			text = src.TextValue.UserValue
			if src.Required {
				if len(text) == 0 {
					option.VXMLs.Append("you have not entered any text into this text box but you will need to do so before you can submit the form.")
				} else {
					option.VXMLs.Append("you have entered the following text into this text box.")
					option.VXMLs.Append(text)
				}
				option.VXMLs.Append("would you like to enter new text into this text box?")
			} else {
				if len(text) == 0 {
					option.VXMLs.Append("you have not entered any text into this text box.")
				} else {
					option.VXMLs.Append("you have entered the following text into this text box.")
					option.VXMLs.Append(text)
				}
				option.VXMLs.Append("would you like to enter new text into this text box?")
			}
		case conversation.ConversationTypeFormSingleSelect:
			text = data.EmptyString
			for _, fco = range src.MenuOptions {
				if fco.UserSelected {
					text = fco.VXMLs.AtIndex(0)
					break
				}
			}
			if src.Required {
				if len(text) == 0 {
					option.VXMLs.Append("you have not selected an item from this menu but you will need to do so before you can submit the form.")
					option.VXMLs.Append("would you like to select an item from this menu?")
				} else {
					option.VXMLs.Append("you have selected the following item from this menu.")
					option.VXMLs.Append(text)
					option.VXMLs.Append("would you like to select another item from this menu?")
				}
			} else {
				if len(text) == 0 {
					option.VXMLs.Append("you have not selected an item from this menu.")
					option.VXMLs.Append("would you like to select an item from this menu?")
				} else {
					option.VXMLs.Append("you have selected the following item from this menu.")
					option.VXMLs.Append(text)
					option.VXMLs.Append("would you like to select another item from this menu?")
				}
			}
		case conversation.ConversationTypeFormMultipleSelect:
			texts = make([]string, 0, 5)
			for _, fco = range src.MenuOptions {
				if fco.UserSelected {
					texts = append(texts, fco.VXMLs.AtIndex(0))
				}
			}
			count = len(texts)
			if count > 0 {
				text = strings.Join(texts, ", ") + "."
			} else {
				text = data.EmptyString
			}
			if src.Required {
				if count == 0 {
					option.VXMLs.Append("you have not selected any items from this menu but you will need to do so before you can submit the form.")
					option.VXMLs.Append("would you like to select any items from this menu?")
				} else {
					if count == 1 {
						option.VXMLs.Append("you have selected the following item from this menu.")
					} else {
						option.VXMLs.Append(fmt.Sprintf("you have selected the following %d items from this menu.", count))
					}
					option.VXMLs.Append(text)
					option.VXMLs.Append("would you like to select other items from this menu?")
				}
			} else {
				if count == 0 {
					option.VXMLs.Append("you have not selected any items from this menu.")
					option.VXMLs.Append("would you like to select any items from this menu?")
				} else {
					if count == 1 {
						option.VXMLs.Append("you have selected the following item from this menu.")
					} else {
						option.VXMLs.Append(fmt.Sprintf("you have selected the following %d items from this menu.", count))
					}
					option.VXMLs.Append(text)
					option.VXMLs.Append("would you like to select other items from this menu?")
				}
			}
		}
	}
}

// getFormControls builds a list of form controls.
// formcontrols are used to build a conversation for a form and form elements.
// the list may be empty.
func getFormControls(scrubber *scrub.Scrubber, tree *xmlpath.Node, onlyspeakat string) []*param.FormControl {
	var ssn htmlsoup.SliceSoupNode
	var nodes, optionnodes []*htmlsoup.SoupNode
	var formnode, optionnode *htmlsoup.SoupNode
	var path string
	var formid string
	var groups map[string]*param.FormControl
	var formcontrol *param.FormControl
	var option *param.FormControlOption
	var i, length int
	var node *htmlsoup.SoupNode
	var ok bool
	var name, groupname, ntype, required, selected, legend, label, multiple, method string
	var sortedgroupnames []string
	var sortedformcontrols []*param.FormControl
	var vxml string
	sortedformcontrols = make([]*param.FormControl, 0, 5)
	// get the form node
	nodes = conversation.MatchXpathToTreeNodes(scrubber, tree, onlyspeakat)
	formnode = nodes[0]
	if formnode.Name() != "form" {
		return sortedformcontrols
	}
	// have the form node
	// get the controls
	path = fmt.Sprintf("%[1]s//input[@type=\"\" or @type=\"text\" or @type=\"checkbox\" or @type=\"radio\"] | %[1]s//textarea | %[1]s//select", onlyspeakat)
	nodes = conversation.MatchXpathToTreeNodes(scrubber, tree, path)
	if len(nodes) == 0 {
		nodes = conversation.MatchXpathToTreeNodes(scrubber, tree, onlyspeakat)
		if len(nodes) == 1 {
			_, _, formid = formnode.GetAttribute("id")
			path = fmt.Sprintf("//*[@for=\"%s\"", formid)
			nodes = conversation.MatchXpathToTreeNodes(scrubber, tree, path)
		}
	}
	ssn = htmlsoup.BuildSliceSoupNode(0, len(nodes))
	if len(nodes) > 0 {
		ssn.AppendSlice(nodes)
		ssn.Sort()
	}
	// group the nodes
	groups = make(map[string]*param.FormControl)
	sortedgroupnames = make([]string, 0, 5)
	length = ssn.Len()
	for i = 0; i < length; i++ {
		node = ssn.AtIndex(i)
		_, _, name = node.GetAttribute("name")
		switch node.Name() {
		case "input":
			_, _, ntype = node.GetAttribute("type")
			switch ntype {
			case "", "text":
				// add it if the group name is not being used
				groupname = fmt.Sprintf("%s:text", name)
				_, ok = groups[groupname]
				if ok {
					// its being used to create a new group name
					groupname = fmt.Sprintf("%s%d", groupname, i)
				}
				sortedgroupnames = append(sortedgroupnames, groupname)
				formcontrol = param.NewFormControl()
				legend = buildFieldsetLegend(node)
				label = buildLabel(node, scrubber, tree)
				if len(label) == 0 {
					label = "A text box."
				}
				formcontrol.VXML = fmt.Sprintf("%s %s", legend, label)
				formcontrol.Type = param.FormControlTypeText
				formcontrol.Name = name
				_, _, formcontrol.TextValue.DefaultValue = node.GetAttribute("value")
				_, _, required = node.GetAttribute("required")
				formcontrol.Required = len(required) > 0
				groups[groupname] = formcontrol
			case "checkbox":
				// the vxml for a radio group should be the fieldset legend of the first element
				groupname = fmt.Sprintf("%s:checkbox", name)
				formcontrol, ok = groups[groupname]
				if !ok {
					sortedgroupnames = append(sortedgroupnames, groupname)
					formcontrol = param.NewFormControl()
					legend = buildFieldsetLegend(node)
					if len(legend) == 0 {
						legend = "A group of check boxes."
					}
					formcontrol.VXML = legend
					formcontrol.Type = param.FormControlTypeMultipleSelect
					formcontrol.Name = name
					_, _, required = node.GetAttribute("required")
					formcontrol.Required = len(required) > 0
					groups[groupname] = formcontrol
				}
				vxml = buildLabel(node, scrubber, tree)
				if len(vxml) > 0 {
					option = param.NewFormControlOption()
					option.VXMLs.Append(vxml)
					_, _, option.Value = node.GetAttribute("value")
					_, _, selected = node.GetAttribute("checked")
					option.DefaultSelected = len(selected) > 0
					formcontrol.Options = append(formcontrol.Options, option)
				}
			case "radio":
				// the vxml for a radio group should be the fieldset legend of the first element
				groupname = fmt.Sprintf("%s:radio", name)
				formcontrol, ok = groups[groupname]
				if !ok {
					sortedgroupnames = append(sortedgroupnames, groupname)
					formcontrol = param.NewFormControl()
					legend = buildFieldsetLegend(node)
					if len(legend) == 0 {
						legend = "A group of radio buttons."
					}
					formcontrol.VXML = legend
					formcontrol.Type = param.FormControlTypeSingleSelect
					formcontrol.Name = name
					_, _, required = node.GetAttribute("required")
					formcontrol.Required = len(required) > 0
					groups[groupname] = formcontrol
				}
				vxml = buildLabel(node, scrubber, tree)
				if len(vxml) > 0 {
					option = param.NewFormControlOption()
					option.VXMLs.Append(vxml)
					_, _, option.Value = node.GetAttribute("value")
					_, _, selected = node.GetAttribute("selected")
					option.DefaultSelected = len(selected) > 0
					formcontrol.Options = append(formcontrol.Options, option)
				}
			}
		case "textarea":
			// add it if the group name is not being used
			groupname = fmt.Sprintf("%s:textarea", name)
			_, ok = groups[groupname]
			if !ok {
				sortedgroupnames = append(sortedgroupnames, groupname)
				formcontrol = param.NewFormControl()
				legend = buildFieldsetLegend(node)
				label = buildLabel(node, scrubber, tree)
				if len(label) == 0 {
					label = "A text box."
				}
				formcontrol.VXML = fmt.Sprintf("%s %s", legend, label)
				formcontrol.Type = param.FormControlTypeText
				formcontrol.Name = name
				_, _, required = node.GetAttribute("required")
				formcontrol.Required = len(required) > 0
				formcontrol.TextValue.DefaultValue = node.VoiceString()
				groups[groupname] = formcontrol
			}
		case "select":
			// the vxml for a radio group should be the fieldset legend of the first element
			groupname = fmt.Sprintf("%s:select", name)
			formcontrol, ok = groups[groupname]
			if !ok {
				sortedgroupnames = append(sortedgroupnames, groupname)
				formcontrol = param.NewFormControl()
				_, _, multiple = node.GetAttribute("multiple")
				if len(multiple) > 0 {
					formcontrol.Type = param.FormControlTypeMultipleSelect
				} else {
					formcontrol.Type = param.FormControlTypeSingleSelect
				}
				legend = buildFieldsetLegend(node)
				label = buildLabel(node, scrubber, tree)
				if len(label) == 0 {
					label = "A menu box."
				}
				formcontrol.VXML = fmt.Sprintf("%s %s", legend, label)
				formcontrol.Name = name
				_, _, required = node.GetAttribute("required")
				formcontrol.Required = len(required) > 0
				groups[groupname] = formcontrol
			}
			path = fmt.Sprintf("//select[@%s = \"%d\"]/option", data.AWWWEAttributeId, node.ID())
			optionnodes = conversation.MatchXpathToTreeNodes(scrubber, tree, path)
			for _, optionnode = range optionnodes {
				option = param.NewFormControlOption()
				option.VXMLs.Append(optionnode.VoiceString())
				// get the value from the value attr or the option text
				if optionnode.HasAttribute("value") == true {
					_, _, option.Value = node.GetAttribute("value")
				} else {
					option.Value = strings.Trim(optionnode.TextString(), data.WhiteSpaceString)
				}
				_, _, selected = node.GetAttribute("selected")
				option.DefaultSelected = len(selected) > 0
				formcontrol.Options = append(formcontrol.Options, option)
			}
		case data.EmptyString:
			// ignore
		}
	}
	// return a sorted list of the form control groups.
	for _, name = range sortedgroupnames {
		formcontrol = groups[name]
		switch formcontrol.Type {
		case param.FormControlTypeSingleSelect, param.FormControlTypeMultipleSelect:
			if len(formcontrol.Options) > 0 {
				sortedformcontrols = append(sortedformcontrols, formcontrol)
			} else {
			}
		case param.FormControlTypeText:
			sortedformcontrols = append(sortedformcontrols, formcontrol)
		}
	}
	if len(sortedformcontrols) > 0 {
		// add the submit form control
		formcontrol = param.NewFormControl()
		formcontrol.Type = param.FormControlTypeSubmit
		_, _, formcontrol.Action = formnode.GetAttribute("action")
		_, _, method = formnode.GetAttribute("method")
		if len(method) == 0 {
			method = "get"
		} else {
			formcontrol.Method = strings.ToLower(method)
		}
		sortedformcontrols = append(sortedformcontrols, formcontrol)
	}
	return sortedformcontrols
}

// buildLabel returns only the voice from the label for a form element.
func buildLabel(node *htmlsoup.SoupNode, scrubber *scrub.Scrubber, tree *xmlpath.Node) string {
	var parent *htmlsoup.SoupNode
	var nodes []*htmlsoup.SoupNode
	var path string
	var id string
	var isvisible bool
	var vxml string
	// find the label and get its voice string
	parent = node.Parent()
	if parent.Name() == "label" {
		// the label wraps around the input which could be a textarea
		// get the text before or after the input node.
		isvisible = node.IsVisible()
		node.SetVisibility(false)
		vxml = parent.VoiceString()
		node.SetVisibility(isvisible)
		return vxml
	} else {
		// must find the label using for
		_, _, id = node.GetAttribute("id")
		if len(id) > 0 {
			path = fmt.Sprintf("//label[@for=\"%s\"]", id)
			nodes = conversation.MatchXpathToTreeNodes(scrubber, tree, path)
			if len(nodes) == 1 {
				return nodes[0].VoiceString()
			}
		}
	}
	return data.EmptyString
}

// buildFieldsetLegend returns the comma separated legends for a form element.
func buildFieldsetLegend(node *htmlsoup.SoupNode) string {
	var parent, child *htmlsoup.SoupNode
	var children []*htmlsoup.SoupNode
	var i, length int
	var legends data.SliceString
	legends = data.BuildSliceString(0, 5)
	if node.IsVisible() {
		for {
			parent = node.Parent()
			if parent == nil || parent.Name() == "form" {
				break
			} else if parent.Name() == "fieldset" {
				// now find the legend
				children = parent.Children()
				length = len(children)
				for i = 0; i < length; i++ {
					child = children[i]
					if child.Name() == "legend" {
						legends.Append(child.VoiceString())
						break
					}
				}
			}
			node = parent
		}
	}
	legends.Reverse()
	return strings.Join(legends.Slice(), ", ")
}

// buildFormElementConversation initializes a new Conversation for a form element based on the *param.FormControl
func buildFormElementConversation(formcontrol *param.FormControl) *conversation.Conversation {
	var conv *conversation.Conversation
	conv = conversation.NewConversation()
	// type
	switch formcontrol.Type {
	case param.FormControlTypeText:
		conv.Type = conversation.ConversationTypeFormEnterText
		conv.Name = formcontrol.Name
		conv.Required = formcontrol.Required
		conv.Header.Append(fmt.Sprintf("this is the text box labeled, %s", formcontrol.VXML))
		if setting.BrowserSettings.HelpLevel != setting.SettingHelpLevel3 {
			// a lot of help or a little help.
			// add helpful
			conv.Helpful = " Speak the text that you want to enter into this text box."
		}
		if setting.BrowserSettings.HelpLevel == setting.SettingHelpLevel1 {
			// a lot of help
			// add more helpful
			conv.MoreHelpful = " You may begin speaking now."
		}
	case param.FormControlTypeSingleSelect:
		conv.Type = conversation.ConversationTypeFormSingleSelect
		conv.Name = formcontrol.Name
		conv.Required = formcontrol.Required
		conv.Header.Append(fmt.Sprintf("this is the menu labeled, %s", formcontrol.VXML))
		if setting.BrowserSettings.HelpLevel != setting.SettingHelpLevel3 {
			// a lot of help or a little help.
			// add helpful
			conv.Helpful = " this menu allows you to select only one item. it also allows you to select and unselect an item as many times as you need to."
		}
		if setting.BrowserSettings.HelpLevel == setting.SettingHelpLevel1 {
			// a lot of help
			// add more helpful
			conv.MoreHelpful = " i will speak each item to you, tell you if it is selected or not, and then ask you if you want to change your selection. You may say, yes, back, restart, finish, help, pause, or quit."
		}
		conv.Footer = " that is the end of the list of menu items."
		conv.MenuOptions = formcontrol.Options
	case param.FormControlTypeMultipleSelect:
		conv.Type = conversation.ConversationTypeFormMultipleSelect
		conv.Name = formcontrol.Name
		conv.Required = formcontrol.Required
		conv.Header.Append(fmt.Sprintf("this is the menu labeled, %s", formcontrol.VXML))
		if setting.BrowserSettings.HelpLevel != setting.SettingHelpLevel3 {
			// a lot of help or a little help.
			// add helpful
			conv.Helpful = " this menu allows you to select one or more items. it also allows you to select and unselect an item as many times as you need to."
		}
		if setting.BrowserSettings.HelpLevel == setting.SettingHelpLevel1 {
			// a lot of help
			// add more helpful
			conv.MoreHelpful = " i will speak each item to you, tell you if it is selected or not, and then ask you if you want to change your selection. You may say, yes, back, restart, finish, help, pause, or quit."
		}
		conv.Footer = " that is the end of the list of menu items."
		conv.MenuOptions = formcontrol.Options
	case param.FormControlTypeSubmit:
		conv.Type = conversation.ConversationTypeYesNo
		conv.Action = formcontrol.Action
		conv.Method = formcontrol.Method
		conv.Header.Append("this is the submit button.")
		if setting.BrowserSettings.HelpLevel != setting.SettingHelpLevel3 {
			// a lot of help or a little help.
			// add helpful
			conv.Helpful = " do you want to submit this form now?"
		}
		if setting.BrowserSettings.HelpLevel == setting.SettingHelpLevel1 {
			// a lot of help
			// add more helpful
			conv.MoreHelpful = " You may say, yes, no, back, restart, finish, help, pause, or quit."
		}
		conv.Footer = data.EmptyString
	}
	return conv
}
